/*
 * SignalBroker.hpp
 *
 *  Created on: Apr 29, 2009
 *      Author: asantos
 */

#ifndef SIGNALBROKER_HPP_
#define SIGNALBROKER_HPP_

#include <string>
#include <map>
#include <boost/signals.hpp>


template<typename Sig_Tp>
class Signal;

class UnableToCastSignal : public virtual std::exception{
private:
		const char* msg;
public:
		UnableToCastSignal(const char* msg):msg(msg){

	}
	char const* what()const throw(){
		return msg;
	}
};

class SignalBase{
public:
	virtual ~SignalBase(){}
	template<typename Sig_Tp>
	const Signal<Sig_Tp>& GetSignal()const{
		Signal<Sig_Tp>* signal = dynamic_cast<Signal<Sig_Tp>*>(this);
		if(signal){
			return *signal;
		}else{
			throw UnableToCastSignal("");
		}
	}
	template<typename Sig_Tp>
	Signal<Sig_Tp>& GetSignal(){
		Signal<Sig_Tp>* signal = dynamic_cast<Signal<Sig_Tp>*>(this);
		if(signal){
			return *signal;
		}else{
			throw UnableToCastSignal("");
		}
	}
};

template<typename Sig_Tp>
class Signal: public SignalBase, public boost::signal<Sig_Tp>{
public:
	virtual ~Signal(){}
};

class SignalAlreadyExists : public virtual std::exception{
public:
	SignalAlreadyExists(const char* msg){

	}
	char const* what()const throw(){
		return "SignalAlreadyExists";
	}
};
class SignalDoesNotExist : public virtual std::exception{
private:
		const char* msg;
public:
	SignalDoesNotExist(const char* msg):msg(msg){

	}
	char const* what()const throw(){
		return msg;
	}
};

class SignalBroker{
	private:
	typedef std::map<std::string, SignalBase*> Signals;
	Signals signals;

	public:
	SignalBroker(){}
	bool HasSignal(const std::string& name)const{
		return signals.find(name)!=signals.end();
	}
	template<typename Sig_Tp>
	Signal<Sig_Tp>& CreateSignal(const std::string& name){
		Signal<Sig_Tp>* signal = 0;
		if(signals.find(name)==signals.end()){
			signal = new Signal<Sig_Tp>();
			SignalBase* signalbase = static_cast<SignalBase*>(signal);
			signals.insert(Signals::value_type(name, signalbase));
			return *signal;
		}
		else{
			throw SignalAlreadyExists(name.c_str());
		}
	}
	template<typename Sig_Tp>
	Signal<Sig_Tp>& CreateOrGetExistingSignal(const std::string& name){
		if(signals.find(name)!=signals.end()){
			return signals.find(name)->second->GetSignal<Sig_Tp>();
		}
		else{
			Signal<Sig_Tp>* signal = new Signal<Sig_Tp>();
			signals.insert(Signals::value_type(name, static_cast<SignalBase*>(signal)));
			return *signal;
		}
	}

	void DeleteSignal(const std::string& name){
		if(signals.find(name)==signals.end()){
			delete signals.find(name)->second;
			signals.erase(name);
		}
		else{
			throw SignalDoesNotExist(name.c_str());
		}
	}
	template<typename Sig_Tp>
	Signal<Sig_Tp>& GetSignal(const std::string& name){
		if(signals.find(name)!=signals.end()){
			return signals.find(name)->second->GetSignal<Sig_Tp>();
		}
		else{
			throw SignalDoesNotExist(name.c_str());
		}
	}
	template<typename Sig_Tp>
	const Signal<Sig_Tp>& GetSignal(const std::string& name)const{
		if(signals.find(name)!=signals.end()){
			return signals.find(name)->second->GetSignal<Sig_Tp>();
		}
		else{
			throw SignalDoesNotExist(name.c_str());
		}
	}
	template<typename Fn_Tp>
	void InvokeSignal(const std::string& name){
		GetSignal<Fn_Tp>(name)();
	}
	template<typename Fn_Tp>
	void InvokeSignal(const std::string& name, typename boost::function<Fn_Tp>::arg1_type arg1){
		GetSignal<Fn_Tp>(name)(arg1);
	}
	template<typename Fn_Tp>
	void InvokeSignal(const std::string& name, typename boost::function<Fn_Tp>::arg1_type arg1, typename boost::function<Fn_Tp>::arg2_type arg2){
		GetSignal<Fn_Tp>(name)(arg1, arg2);
	}
	template<typename Fn_Tp>
	void InvokeSignal(const std::string& name, typename boost::function<Fn_Tp>::arg1_type arg1, typename boost::function<Fn_Tp>::arg2_type arg2, typename boost::function<Fn_Tp>::arg3_type arg3){
		GetSignal<Fn_Tp>(name)(arg1, arg2, arg3);
	}
	template<typename Fn_Tp>
	void InvokeSignal(const std::string& name, typename boost::function<Fn_Tp>::arg1_type arg1, typename boost::function<Fn_Tp>::arg2_type arg2, typename boost::function<Fn_Tp>::arg3_type arg3, typename boost::function<Fn_Tp>::arg4_type arg4){
		GetSignal<Fn_Tp>(name)(arg1, arg2, arg3, arg4);
	}
	template<typename Fn_Tp>
	void InvokeSignal(const std::string& name, typename boost::function<Fn_Tp>::arg1_type arg1, typename boost::function<Fn_Tp>::arg2_type arg2, typename boost::function<Fn_Tp>::arg3_type arg3, typename boost::function<Fn_Tp>::arg4_type arg4, typename boost::function<Fn_Tp>::arg5_type arg5){
		GetSignal<Fn_Tp>(name)(arg1, arg2, arg3, arg4, arg5);
	}
};

#endif /* SIGNALBROKER_HPP_ */

